package com.by.basicgateway.config;

import com.alibaba.fastjson2.JSON;
import com.by.basicgateway.enums.RequestorClientTypeEnum;
import com.by.basicgateway.exception.ByAuthenticationServiceException;
import com.by.basicgateway.intercept.filter.CustomizeRequestHandlerFilter;
import com.by.basicgateway.security.UserAccessDeniedHandler;
import com.by.basicgateway.security.UserAuthenticationManager;
import com.by.basicgateway.security.UserAuthorizationManager;
import com.by.basicgateway.support.TokenHandlerSupport;
import com.by.toolkit.enums.ErrorCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;


@Slf4j
@Configuration
@EnableWebFluxSecurity
public class WebFluxSecurityConfig {


    @Autowired
    private UserAuthorizationManager userAuthorizationManager;

    @Autowired
    private UserAccessDeniedHandler userAccessDeniedHandler;

    @Autowired
    private UserAuthenticationManager userAuthenticationManager;

    @Autowired
    private TokenHandlerSupport tokenHandlerSupport;

    @Autowired
    private ByServerWebExchangeMatcher matcherRule;

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        //SecurityWebFiltersOrder枚举类定义了执行次序
        http.securityMatcher(matcherRule)
                .authorizeExchange(exchanges -> {
                    exchanges
                            .matchers(matcherRule)
                            .permitAll()
                            .anyExchange()
                            .access(userAuthorizationManager);
                })
                .addFilterAt(authenticationWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)//认证配置
                .addFilterAfter(requestHandlerFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
                .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .formLogin(ServerHttpSecurity.FormLoginSpec::disable)
                .logout(ServerHttpSecurity.LogoutSpec::disable)
                .requestCache(ServerHttpSecurity.RequestCacheSpec::disable)
                //授权或认证异常失败处理
                .exceptionHandling(exceptionHandlingSpec -> {
                    exceptionHandlingSpec.authenticationEntryPoint(userAccessDeniedHandler)
                            .accessDeniedHandler(userAccessDeniedHandler);
                })
        ;
        return http.build();
    }

    @Bean
    public AuthenticationWebFilter authenticationWebFilter() {
        AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(userAuthenticationManager);
        //数据转换
        authenticationWebFilter.setServerAuthenticationConverter(exchange -> {
            String clientHeaderValue = exchange.getRequest()
                    .getHeaders()
                    .getFirst(RequestorClientTypeEnum.CLIENT_HEADER_NAME);

            if (StringUtils.isEmpty(clientHeaderValue)) {
                throw new ByAuthenticationServiceException(ErrorCodeEnum.CLIENT_REQUEST_NOT_EXIST.getErrorMessage(), ErrorCodeEnum.CLIENT_REQUEST_NOT_EXIST);
            }

            RequestorClientTypeEnum requestorClientTypeEnum = RequestorClientTypeEnum.resolveRequestorClientTypeEnum(clientHeaderValue);
            if (Objects.isNull(requestorClientTypeEnum)) {
                throw new ByAuthenticationServiceException(ErrorCodeEnum.CLIENT_REQUEST_NOT_EXIST.getErrorMessage(), ErrorCodeEnum.CLIENT_REQUEST_NOT_EXIST);
            }
            return Mono.just(tokenHandlerSupport.convertRequestToMyAuthentication(exchange, requestorClientTypeEnum));
        });

        authenticationWebFilter.setAuthenticationSuccessHandler((webFilterExchange, authentication) -> {
            log.info("[WebFluxSecurityConfig][onAuthenticationSuccess] token 数据校验成功");
            return webFilterExchange.getChain().filter(webFilterExchange.getExchange());
        });
        authenticationWebFilter.setAuthenticationFailureHandler((webFilterExchange, exception) -> {
            ByAuthenticationServiceException authenticationServiceException = (ByAuthenticationServiceException) exception;
            ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
            response.setStatusCode(HttpStatus.OK);
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            Map<String, Object> map = new HashMap<>();
            map.put("code", authenticationServiceException.getErrorCodeEnum().getErrorCode());
            map.put("msg", authenticationServiceException.getErrorCodeEnum().getErrorMessage());
            log.info("access forbidden path={},excepton:{}", webFilterExchange.getExchange().getRequest().getPath(), authenticationServiceException.getMessage());
            DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(map));
            return response.writeWith(Mono.just(dataBuffer));
        });
        authenticationWebFilter.setRequiresAuthenticationMatcher(matcherRule);
        return authenticationWebFilter;
    }

    @Bean
    public CustomizeRequestHandlerFilter requestHandlerFilter() {
        return new CustomizeRequestHandlerFilter(matcherRule);
    }

    @Bean
    public UrlBasedCorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOriginPatterns(Collections.singletonList("*"));
        configuration.addAllowedOrigin("*");
        configuration.addAllowedMethod("*");
        configuration.addAllowedHeader("*");
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}
